package vip.liux.backend.infrastructure.jpa.pay;

import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.transaction.annotation.Transactional;
import vip.liux.contracts.models.pay.PayOrder;
import vip.liux.contracts.repositories.pay.PayOrderRepository;
import vip.liux.contracts.shared.enums.pay.PayOrderStatus;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

public interface PayOrderJpaRepository extends JpaRepository<PayOrder, Long>, PayOrderRepository, JpaSpecificationExecutor<PayOrder> {

    @Override
    default Page<PayOrder> findPageBy(Long appId, String channelCode, String merchantOrderId, String orderNo, String channelOrderNo, PayOrderStatus status, LocalDateTime start, LocalDateTime end, Pageable page) {
        return findAll(buildSpecification(appId, channelCode, merchantOrderId, orderNo, channelOrderNo, status, start, end), page);
    }

    @Override
    default List<PayOrder> findListBy(Long appId, String channelCode, String merchantOrderId, String orderNo, String channelOrderNo, PayOrderStatus status, LocalDateTime start, LocalDateTime end) {
        return findAll(buildSpecification(appId, channelCode, merchantOrderId, orderNo, channelOrderNo, status, start, end));
    }

    @Modifying
    @Override
    @Transactional
    @Query("update PayOrder set refundPrice = :refundPrice where orderNo = :orderNo and status = :status")
    int updateByIdAndStatus(String orderNo, PayOrderStatus status, BigDecimal refundPrice);

    @Modifying
    @Override
    @Transactional
    @Query("update PayOrder set price = :payPrice where id = :id")
    void updateByIdSetPrice(Long id, BigDecimal payPrice);

    @Query("select o from PayOrder o where o.status = :status and o.expireTime >= :expireTime")
    @Override
    List<PayOrder> selectListByStatusAndExpireTimeLt(PayOrderStatus status, LocalDateTime expireTime);

    @Modifying
    @Transactional
    @Query("update PayOrder set status = :payOrderStatus, channelNotifyData = :channelNotifyData, " +
            "channelErrorCode = :channelErrorCode, channelErrorMsg = :channelErrorMsg," +
            "channelId =:channelId, channelCode =:channelCode  " +
            "where id = :id and orderNo = :orderNo and status = :status")
    int updateOrderClosed(Long id, String orderNo, Long channelId, String channelCode, PayOrderStatus status, PayOrderStatus payOrderStatus, String channelNotifyData, String channelErrorCode, String channelErrorMsg);

    @Modifying
    @Transactional
    @Query("update PayOrder set status = :updateStatus, channelId = :channelId, channelCode = :channelCode, " +
            "successTime = :successTime, channelOrderNo = :channelOrderNo, channelUid = :channelUid, " +
            "channelFeeRate = :channelFeeRate, channelFeePrice = :channelFeePrice, channelNotifyData = :channelNotifyData " +
            "where id = :id and orderNo = :orderNo and status = :currentStatus")
    int updateOrderSuccess(Long id, String orderNo, PayOrderStatus currentStatus, PayOrderStatus updateStatus, Long channelId, String channelCode, LocalDateTime successTime, String channelOrderNo, String channelUid, Double channelFeeRate, BigDecimal channelFeePrice, String channelNotifyData);

    /**
     * 构建查询条件的公共方法
     */
    private Specification<PayOrder> buildSpecification(Long appId, String channelCode, String merchantOrderId, String orderNo, String channelOrderNo, PayOrderStatus status, LocalDateTime start, LocalDateTime end) {
        return (root, query, criteriaBuilder) -> {
            List<Predicate> predicates = new ArrayList<>();

            addEqualsPredicate(predicates, criteriaBuilder, root, "appId", appId);
            addEqualsPredicate(predicates, criteriaBuilder, root, "channelCode", channelCode);
            addEqualsPredicate(predicates, criteriaBuilder, root, "merchantOrderId", merchantOrderId);
            addEqualsPredicate(predicates, criteriaBuilder, root, "orderNo", orderNo);
            addEqualsPredicate(predicates, criteriaBuilder, root, "status", status);
            addEqualsPredicate(predicates, criteriaBuilder, root, "channelOrderNo", channelOrderNo);

            if (start != null) {
                predicates.add(criteriaBuilder.greaterThanOrEqualTo(root.get("creationTime"), start));
            }

            if (end != null) {
                predicates.add(criteriaBuilder.lessThanOrEqualTo(root.get("creationTime"), end));
            }

            return query.where(predicates.toArray(new Predicate[0])).getRestriction();
        };
    }

    /**
     * 添加相等条件的辅助方法
     */
    private <T> void addEqualsPredicate(List<Predicate> predicates, CriteriaBuilder cb, Root<PayOrder> root, String field, T value) {
        if (value != null) {
            if (value instanceof String && StringUtils.isBlank((String) value)) {
                return;
            }
            predicates.add(cb.equal(root.get(field), value));
        }
    }

    /**
     * 添加模糊查询条件的辅助方法
     */
    private void addLikePredicate(List<Predicate> predicates, CriteriaBuilder cb, Root<PayOrder> root, String field, String value) {
        if (StringUtils.isNotBlank(value)) {
            predicates.add(cb.like(root.get(field), value));
        }
    }
}